#!/usr/bin/env python
# -*- coding: utf-8 -*- 
#############################################################################
# GMangas
# Copyright (C) 2008  Ferraro Luciano (aka Lux) <luciano.ferraro@gmail.com>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
############################################################################

import sys
import os, os.path
import shutil
import thread
import time
try: import cPickle as pickle
except: import pickle

import __init__

import utils
utils.gmangas = globals()

__verbose__ = True
curdir = utils.get_path_from_filename("gmangas.py")
homedir = os.path.join(os.path.expanduser("~"), ".gmangas")
utils.create_dir(homedir)

if all((sys.platform.startswith("win"), os.path.isdir(os.path.join(curdir, "GTK")))):
    curdir = utils.get_path_from_filename("gmangas.exe")
    __verbose__ = False
    os.environ["PATH"] += ";" + os.path.join(curdir, "GTK")
    os.environ["PATH"] += ";" + os.path.join(curdir, "GTK", "lib")

import pygtk
pygtk.require("2.0")
import gtk
import gtk.glade
import gobject

gtk.gdk.threads_init()

__buildname__ = "GMangas (jiraya build)"
__version__ = "SNAPSHOT"
__url__ = "http://code.google.com/p/ferraroluciano/source/browse/trunk/GMangas/src/"
__copyright__ = "GPL v3"
__license__ = lambda: open(os.path.join(curdir, "LICENSE")).read()
__authors__ = lambda: open(os.path.join(curdir, "AUTHORS")).read().splitlines()

#WORKAROUND FOR WIDESCREENS
__width__, __height__ = lambda x=0: int(gtk.gdk.screen_width()/(2.45, 2.95)[gtk.gdk.screen_width()==1680]), lambda x=0: int(gtk.gdk.screen_height()/1.2)

__gladefile__ = os.path.join(curdir, "glade", "gmangas.glade")
__icon__ = os.path.join(curdir, "pixmaps", "cheff.png")
__startimage__ = os.path.join(curdir, "pixmaps", "cheff.png")

import window
import image
import buttons
import combobox
import statusbar
import menubar
import about
import errhandler

if not __verbose__:
    stdout = file(os.path.join(homedir, "logs.txt"), "w")
    sys.stdout = stdout

def poll(stack):
    while not len(stack):
        gtk.main_iteration()
    while len(stack):
        cback = stack.pop()
        cback()

def nop(*x):
    print "Something gone crazy. PANIC!"

class Core:
    def __init__(self, gm):
        self.gm = gm
        self.image = gm.image

        self.optimal_scale_rate = 0

        self.manga, self.chapter, self.page, self.pages = zip([None]*4, [None]*4)
        self.reset_statusbar = lambda x=0: self.statusbar.change_text(_("Done =)!"))
        self.html_cache_dir = os.path.join(homedir, "html_cache", BackEnd.name)
        utils.create_dir(self.html_cache_dir)

        self.page_cache = []
        self.page_cache_size = 8

    def update_image(self, nocache=False):
        try:
            self.image.set_image(self.images_path, nocache)
        except gobject.GError:
            os.remove(self.images_path)
            self.gm.start_new(self.start, [None])

    def optimal_scale(self):
        mw, mh = map(float, self.window._.get_size())
        iw, ih = self.image.width, self.image.height

        return min(1, min(mw/iw, mh/ih)) + self.optimal_scale_rate

    def download(self, url, ext=None):
        def _write(_file, data):
            _file = open(_file, 'wb')
            _file.write(data)
            _file.close()
        dir = os.path.join(self.html_cache_dir, self.manga[0] or '')
        _file = os.path.join(dir, url.replace('/', ''))
        write = lambda data: _write(_file, data)
        if not url: _file = os.path.join(_file, "main")
        if not os.path.isdir(dir):
            utils.create_dir(dir)
        elif os.path.isfile(_file):
            print "Hit cache %s" % repr(_file)
            data = open(_file, 'r').read()
            return data
        url = BackEnd.url + url + (ext or '')
        #if self.check_download(url):
        #    self.downloaded_data = None
        #    utils.downloads_list[url].append(write)
        #    while not self.downloaded_data:
        #        time.sleep(0.1)
        data = utils.download_page(url)
        write(data)
        return data

    def dir_cache(self):
        path = os.path.join(self.html_cache_dir, "DIR_DUMP")
        if os.path.isfile(path):
            fh = open(path, 'r')
            return pickle.load(fh)
        elif not os.path.isdir(self.html_cache_dir):
            utils.create_dir(self.html_cache_dir)
        else: return None

    def set_dir_cache(self, data):
        path = os.path.join(self.html_cache_dir, "DIR_DUMP")
        fh = open(path, "w")
        pickle.dump(data, fh)

    def update_mangas(self, cback, stack):
        callback = nop

        try:
            dircache = self.dir_cache()
            if not dircache:
                data = self.download(BackEnd.mangas_url, BackEnd.mangas_url_ext)
                text, urls = BackEnd.parse_mangas(data)
                urls = dict(zip(text, urls))
                self.set_dir_cache(urls)
            else:
                urls = dircache

            def callback():
                self.combobox.clear("manga")
                self.combobox.clear("chapter")
                self.combobox.clear("page")
                self.urls = urls
                for val in sorted(urls.keys()):
                    self.combobox.add_value(val, "manga")

                self.sensitive(True)
                self.reset_statusbar()
                if cback: cback()

        finally:
            stack.append(callback)

    def _mangas_before(self):
        self.statusbar.change_text(_("Loading Mangas list..."))
    update_mangas.before = _mangas_before

    def update_chapters(self, manga, cback, stack):
        callback = nop

        try:
            data = self.download(self.urls[manga])
            text, values = BackEnd.parse_chapters(data)
            def callback():
                self.combobox.clear("chapter")
                self.combobox.clear("page")
                self.values = dict(zip(text, values))
                for val in text:
                    self.combobox.add_value(val, "chapter")

                self.sensitive(True)
                self.reset_statusbar()
                if cback: cback()
            
        finally:
            stack.append(callback)

    def _chapter_before(self):
        manga = self.manga[0]
        self.statusbar.change_text(_("Loading %s's chapters...") % manga)

    update_chapters.before = _chapter_before
    
    def update_pages(self, manga, chapter, cback, stack):
        callback = nop

        try:
            data = self.download(self.values[chapter])
            def callback():
                options, urls = BackEnd.parse_pages(data)
                self.pages, self.rpages = {}, {}
                self.options = dict(zip(options, urls))
                for count, value in enumerate(options):
                    self.pages[value] = count
                    self.rpages[count] = value
                    self.combobox.add_value(value, "page")

                self.sensitive(True)
                self.reset_statusbar()
                if cback: cback()
                
        finally:
            stack.append(callback)

    def _pages_before(self):
        manga, chapter = self.manga[0], self.chapter[0]
        self.statusbar.change_text(
            _("Loading chapter's (%(chapter)s) pages of %(manga)s...") % {
                    'chapter':chapter, 'manga':manga
                    }
            )
        self.combobox.clear("page")

    update_pages.before = _pages_before

    def load_image(self, page, chapter, manga, cback, stack):
        callback = nop

        key = (page, chapter, manga)
        try:
            url = utils.assoc(key, self.page_cache)
            if not url:
                data = self.download(self.values[chapter] + self.options[page])
                url = BackEnd.parse_images_url(data, page)
            images_path = os.path.join(homedir, "mangas", BackEnd.name,
                                       manga, chapter, os.path.basename(url))
            if not os.path.isfile(images_path):
                utils.create_dir(os.path.dirname(images_path))
                _file = open(images_path, 'wb')
                _file.write(utils.download_page(url))
                _file.close()

            def callback():
                utils.pushnew((key, url), self.page_cache)
                if cback: cback(images_path, url)
                
        finally:
            stack.append(callback)

    def download_all_images(self, chapter, manga, cback, stack):
        self.image._.set_sensitive(False)
        key = ['', chapter or self.chapter[0], manga or self.manga[0]]
        for count, page in enumerate(self.pages.keys()):
            key[0] = page
            self.load_image(*(key+[None, stack]))

        self.sensitive(True, True)
        self.reset_statusbar()
        if cback: cback()

    def _download_all_images_before(self):
        self.statusbar.change_text(
            _("Downloading all images of current chapter of %s... will take A LOTS") %
            self.manga[0])

    download_all_images.before = _download_all_images_before

    def start(self, cback, stack):
        key = [self.page[0], self.chapter[0], self.manga[0]]
        def callback(images_path, url):
            self.page_cache = self.page_cache[- self.page_cache_size : ]
            self.images_path = images_path
            self.images_url = url
            self.update_image()

            self.sensitive(True)
            self.reset_statusbar()
            if cback: cback()
        
        self.load_image(*(key+[callback, stack]))

    def _start_before(self):
        page, chapter, manga = self.page[0], self.chapter[0], self.manga[0]
        self.statusbar.change_text(
            _("Downloading page's (%(page)s) image of chapter (%(chapter)s) of %(manga)s...") % {
                    'page':page, 'chapter':chapter, 'manga':manga
                    }
            )

    start.before = _start_before

    def clear_all(self):
        shutil.rmtree(self.html_cache_dir)
        self.gm.start_new(self.update_mangas, [None])

    def clear_current_manga(self, manga):
        dir = os.path.join(self.html_cache_dir, manga)
        shutil.rmtree(dir)
        self.gm.start_new(self.update_chapters, [manga, None])

class GMangas:
    def __init__(self):
        self.root = gtk.glade.XML(__gladefile__)

        self.title = "GMangas @ "+BackEnd.name
        self.window = window.Main(self, self.root, self.title, (__width__(), __height__()), __icon__)
        self.image = image.Main(self, self.root)
        self.buttons = buttons.Main(self, self.root)
        self.combobox = combobox.Main(self, self.root)
        self.statusbar = statusbar.Main(self, self.root)
        self.menubar = menubar.Main(self, self.root)
        self.about = about.Main(self, globals())
        self.eventbox = self.root.get_widget("eventbox")
        self.errhandler = errhandler.Main()
        self.errhandler.start()

        self.connections = {
            'on_manga_changed':self.changed_manga, 
            'on_chapter_changed':self.changed_chapter,
            'on_page_changed':self.changed_page, 
            'on_menubar_toggle_toggled':self.menubar_toggled,
            'on_close_clicked':self.window.quit, 
            'on_forward_press':self.forward_pressed,
            'on_back_press':self.back_pressed, 
            'on_menu_cache-html_activate':self.menu_html_activated,
            'on_menu_cache-mangas_activate':self.menu_mangas_activated,
            'on_zoomin_press':self.zoomin_pressed, 
            'on_zoomout_press':self.zoomout_pressed,
            'on_menu_help-about_activate':self.about.run, 
            'on_bestfit_press':self.bestfit_pressed, 
            'on_menu_scripts-download_all_chapters_activate':self.menu_download_all_chapters_activated,
            'on_menu_scripts-download_all_pages_activate':self.menu_download_all_pages_activated,
            'on_eventbox_press':self.eventbox_pressed, 
            'on_refresh_press':self.refresh_pressed,
            'on_fullscreen_toggled':self.fullscreen_toggled
            }
        self.root.signal_autoconnect(self.connections)

        self.core = Core(self)
        BackEnd.core = self.core

        # OBNOXIOUS.
        vars = ["window", "image", "buttons", "combobox", "eventbox", "sensitive", "statusbar", "errhandler"]
        for var in vars:
            setattr(self.core, var, getattr(self, var))

        self.widgets = ["buttons.back", "buttons.forward", "buttons.refresh_button", 
                        "combobox.manga", "combobox.page", "combobox.chapter"]
        self.preload_size = 2

        self.image.set_image(__startimage__)
        self.core.images_path = __startimage__

    def sensitive(self, bool, image=False):
        if image:
            self.image._.set_sensitive(bool)
        for var in self.widgets:
            if var.find(".") == -1:
                widget = getattr(self, var)
            else:
                widget = getattr(getattr(self, var.split(".")[0]), var.split(".")[1])
            set = widget.set_sensitive(bool)

    def start_new(self, func, args, use_threads=True, update_statusbar=True, image_sensitive=False, change_sensivitive=True):
        if change_sensivitive:
            self.sensitive(False, image_sensitive)
        try: 
            if update_statusbar:
                func.before(self.core)
        except AttributeError: pass
        stack = []
        args.append(stack)
        if use_threads:
            thread.start_new_thread(func, tuple(args))
        else:
            func(*tuple(args))
        poll(stack)

    def preloader(self, active_text, active_iter):
		# # DISABLED # #
        if True: return
        model = self.combobox.page.get_model()
        key = ['', self.core.chapter[0], self.core.manga[0]]
        count = 0
        while all((self.core.page[0] == active_text, count <= self.preload_size)):
            count += 1
            active_iter = model.iter_next(active_iter)
            if not active_iter: break
            key[0] = model.get_value(active_iter, 0)
            self.start_new(self.core.load_image, key+[None],
                           False, False, False, False)

    def changed_manga(self, cbox):
        self.core.manga = [cbox.get_active_text(), cbox.get_active_iter()]
        if self.checker("manga"):
            return None

        self.start_new(self.core.update_chapters, [self.core.manga[0], None])

    def changed_chapter(self, cbox):
        self.core.chapter = [cbox.get_active_text(), cbox.get_active_iter()]
        if self.checker("chapter"):
            return None

        self.start_new(self.core.update_pages,
                        [self.core.manga[0], self.core.chapter[0], None])
        self.combobox.page.set_active(1)

    def changed_page(self, cbox):
        active_text, active_iter = cbox.get_active_text(), cbox.get_active_iter()
        self.core.page = [active_text, active_iter]
        if self.checker("page"):
            return None
        self.start_new(self.core.start, [None])
        thread.start_new(self.preloader, (active_text, active_iter))

    def back_forward(self, type):
        if self.checker("page"):
            return None
        active_text, active_iter = self.combobox.chapter.get_active(), self.combobox.chapter.get_active_iter()
        num = self.core.pages[self.core.page[0]]
        lpages = len(self.core.pages)
        if (num <= 0, num == lpages-1)[type]:
            if all((not self.combobox.chapter.get_model().iter_next(active_iter), type)):
                return
            num = active_text + (-1, 1)[type]
            self.combobox.chapter.set_active(num)
        else:
            num += (-1, 1)[type]
            self.combobox.page.set_active(num + 1)
            self.core.page[0] = self.core.rpages[num]

    def forward_pressed(self, *res):
        self.back_forward(True)

    def back_pressed(self, *res):
        self.back_forward(False)
 
    def eventbox_pressed(self, w, event, *res):
        self.back_forward(not event.button -1)

    def menubar_toggled(self, widget):
        state = widget.get_active()
        if state:
            self.menubar._.show()
        else:
            self.menubar._.hide()

    def menu_html_activated(self, widget):
        self.core.clear_all()

    def menu_mangas_activated(self, widget):
        shutil.rmtree(os.path.join(homedir, "mangas"))

    def download_all_chapters(self, chapter_number):
        # ANOTHER METHOD (not used!!)
        #   * gives a lots of wierd error
        #   * screw up with threads (just start them all togheter)
        #   * probably even faster than the other method, but very unstable
        ##for chapter_number in xrange(self.combobox._["chapter"][1]):
        ##    self.start_new(self.core.update_pages,
        ##                [self.core.manga[0], sorted(self.core.values.keys())[chapter_number-1], None]
        ##                )
        ##    self.start_new(
        ##        self.core.download_all_images,
        ##        [sorted(self.core.values.keys())[chapter_number-1], None, None]
        ##        )
        # USED METHOD
        if chapter_number > self.combobox._["chapter"][1]:
            self.statusbar.unlock()
            return
        cback = lambda x=0: self.start_new(
            self.core.download_all_images,
            [sorted(self.core.values.keys())[chapter_number-1], None, 
             lambda x=0: self.download_all_chapters(chapter_number+1)],
            update_statusbar=False
            )
        self.start_new(self.core.update_pages,
                        [self.core.manga[0], sorted(self.core.values.keys())[chapter_number-1], cback],
                        use_threads=False, update_statusbar=False, image_sensitive=True
                        )

    def menu_download_all_chapters_activated(self, widget):
        if self.checker("manga"):
            return None
        self.statusbar.change_text(
            _("Downloading all chapters of %s... Will take *A LOTS*") % self.core.manga[0])
        self.statusbar.lock()
        self.download_all_chapters(1)

    def menu_download_all_pages_activated(self, widget):
        if not self.checker("chapter"):
            self.start_new(self.core.download_all_images, [self.core.chapter[0], None, None])

    def bestfit_pressed(self, widget, event):
        if self.image.pixbuf:
            if self.core.optimal_scale_rate != 0:
                self.core.optimal_scale_rate = 0
                self.core.update_image(nocache=True)

    def zoomin_pressed(self, widget, event):
        if self.image.pixbuf:
            self.core.optimal_scale_rate += 0.10
            self.core.update_image()

    def zoomout_pressed(self, widget, event):
        if self.image.pixbuf:
            if self.core.optimal_scale() <= 0.10:
                return
            self.core.optimal_scale_rate -= 0.10
            self.core.update_image()

    def refresh_pressed(self, widget, event):
        selected_manga = self.combobox.manga.get_active_text()

        if selected_manga in (_("Select a Manga"), "Select a Manga"):
            self.core.clear_all()
        else:
            self.core.clear_current_manga(selected_manga)

    def fullscreen_toggled(self, widget):
        state = widget.get_active()
        if state:
            self.window._.fullscreen()
        else:
            self.window._.unfullscreen()

    def checker(self, type):
        checker = any((not self.core.manga[0],
                       self.core.manga[0] == _("Select a Manga")))
        if type in ("chapter", "page"):
            checker = any((checker, not self.core.chapter[0],
                           self.core.chapter[0] in (_("Chapter"), "Chapter")))
        if type == "page":
            checker = any((checker, not self.core.page[0],
                           self.core.page[0] in (_("Page"), "Page")))

        return checker

    def start(self):
        self.window.start()
        #self.menubar._.hide()
        self.start_new(self.core.update_mangas, [None])

def start(backend):
    global BackEnd
    BackEnd = __import__(backend)
    gm = GMangas()
    gm.start()
    gtk.main()
    gtk.gdk.threads_leave()
def main():
    global BackEnds
    #Let user's decide a BackEnd
    import backend
    sys.path.append(os.path.join(curdir, "backends"))
    BackEnds = backend.find_backends(curdir)
    gtk.gdk.threads_enter()
    backend.backends_dialog(BackEnds, start, curdir)

if __name__ == "__main__":
    main()
